package main

import "fmt"

func main() {
	orderService := NewOrderService()
	orderService.Save(NewOrder(1, "张三", "广州", "电视", 10))
	orderService.Save(NewOrder(2, "李四", "深圳", "冰箱", 20))
	orderService.Save(NewOrder(3, "王五", "东莞", "空调", 30))
	orderService.Save(NewOrder(4, "张三三", "广州", "空调", 10))
	orderService.Save(NewOrder(5, "李四四", "深圳", "电视", 20))
	orderService.Save(NewOrder(6, "王五五", "东莞", "冰箱", 30))

	cv := NewCityVisitor()
	orderService.Accept(cv)
	cv.Report()

	pv := NewProductVisitor()
	orderService.Accept(pv)
	pv.Report()
}

/*
访问者模式主要适用于以下应用场景:

数据结构稳定，作用于数据结构的操作经常变化的场景。
需要数据结构与数据操作分离的场景。
需要对不同数据类型（元素）进行操作，而不使用分支判断具体类型的场景。
*/

/*
访客接口 （Visitor） 声明了一系列以表示对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载， 这些方法的名称可以是相同的， 但是其参数一定是不同的。
具体访客 （Concrete Visitor） 会为不同的具体元素类实现相同行为的几个不同版本。
元素 （Element） 接口，声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
具体元素 （Concrete Element） 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意， 即使元素基类实现了该方法， 所有子类都必须对其进行重写并调用访客对象中的合适方法。
*/

type IOrderService interface {
	Save(order *Order) error
	Accept(visitor IOrderVisitor)
}

type Order struct {
	ID       int
	Customer string
	City     string
	Product  string
	Quantity int
}

type OrderService struct {
	orders map[int]*Order
}

func NewOrderService() *OrderService {
	return &OrderService{orders: map[int]*Order{}}
}

func (mo *OrderService) Save(o *Order) error {
	mo.orders[o.ID] = o
	return nil
}

func (mo *OrderService) Accept(visitor IOrderVisitor) {
	for _, v := range mo.orders {
		visitor.Visit(v)
	}
}

func NewOrder(id int, customer string, city string, product string, quantity int) *Order {
	return &Order{
		id, customer, city, product, quantity,
	}
}

type IOrderVisitor interface {
	Visit(order *Order)
	Report()
}

type CityVisitor struct {
	cities map[string]int
}

func (cv *CityVisitor) Visit(o *Order) {
	n, ok := cv.cities[o.City]
	if ok {
		cv.cities[o.City] = n + o.Quantity
	} else {
		cv.cities[o.City] = o.Quantity
	}
}

func (cv *CityVisitor) Report() {
	for k, v := range cv.cities {
		fmt.Printf("city = %s, total quantity = %d\n", k, v)
	}
}

func NewCityVisitor() IOrderVisitor {
	return &CityVisitor{
		cities: make(map[string]int, 0),
	}
}

type ProductVisitor struct {
	Products map[string]int
}

func (pv *ProductVisitor) Visit(o *Order) {
	n, ok := pv.Products[o.Product]
	if ok {
		pv.Products[o.Product] = n + o.Quantity
	} else {
		pv.Products[o.Product] = n
	}
}

func (pv *ProductVisitor) Report() {
	for k, v := range pv.Products {
		fmt.Printf("product = %s,total quantity = %d \n", k, v)
	}
}

func NewProductVisitor() IOrderVisitor {
	return &ProductVisitor{
		Products: make(map[string]int, 0),
	}
}
